module SpiceInit
  module Seasoning
    module Instance
      def inspiced(*args,&block)
        # get space for a new object of class‘s class and extract options
        object, options = allocate, args.extract_options!

        # are we tracking spices?
        track_spices = options.has_key?(:track_spices) ? options.delete(:track_spices) : false

        # get readers and accessors or an empty hash
        readers, accessors = options[:readers]||{}, options[:accessors]||{}

        # readers and accessors are both spices
        spices = readers.merge(accessors)

        # get the objet's meta class and add spices
        (class << object;self;end).class_eval do
          # should we keep track of spices added?
          if track_spices
            attr_reader :spices_added
            object.instance_variable_set("@spices_added",[])
          end      
          # loop through all spices
          spices.each do |key,value|

            # first check if we have a punctutated method key
            punctuated = key.to_s.match(/([\!\?]$)/)
            if punctuated
              # if we are here, method key is punctuated
              # writer and variable name are everything but the punctuation mark
              writer = var = punctuated.pre_match
              # the reader equals the writer with punctuation mark
              reader = writer + punctuated[1]
              # we need a reader
              define_method(reader) do
                instance_variable_get("@#{var}")
              end
              # track this spice if required
              object.spices_added << "#{reader}" if track_spices
              # if required, add a writer
              if accessors.has_key?(key)
                attr_writer writer
                # track this spice if required
                object.spices_added << "#{writer}=" if track_spices
              end

              # ready to set the instance variable with assigned value
              object.instance_variable_set("@#{var}", value)
            else
              # if we are here, method key is not punctuated
              # key is the same for reader, writer, and variable name
              if accessors.has_key?(key)
                # if we are here, this key should be an accessor
                attr_accessor key
                # track these spices if required
                object.spices_added << "#{key}" << "#{key}=" if track_spices
              else
                # if we are here, this key should be a reader only
                attr_reader key
                # track this spice if required
                object.spices_added << "#{key}" if track_spices
              end
              # ready to set the instance variable with assigned value
              object.instance_variable_set("@#{key}", value)
            end
          end
        end

        # call initialize but set the block to nil to prevent multiple yields
        object.send(:initialize, *args, &block=nil)

        # if a block is given, pass the object to it
        yield(object) if block_given?

        # return the final object
        object
      end
    end

    module Module
      def modspiced(options={})
        # are we tracking spices?
        track_spices = options.has_key?(:track_spices) ? options.delete(:track_spices) : false

        # get readers and accessors or an empty hash
        readers, accessors = options[:readers]||{}, options[:accessors]||{}

        # readers and accessors are both spices
        spices = readers.merge(accessors)

        if track_spices
          unless respond_to?(:spices_added)
            module_eval %Q{
              def self.spices_added
                @@_spices_added
              end
            }
            class_variable_set("@@_spices_added",[])
          end
        end
        spices.each do |key,value|
          match = key.to_s.match(/([\!\?]$)/)
          if match
            writer = var = match.pre_match
            reader = writer + match[1]
          else
            writer = reader = var = key
          end
          module_eval %Q{
            def self.#{reader}(*args)
              if @@#{var}.is_a?(Proc)
                @@#{var}.call(*args)
              else
                @@#{var}
              end
            rescue
              raise ArgumentError
              caller
            end
          }
          class_variable_set("@@#{var}",value)
          spices_added << "#{reader}" if track_spices && !spices_added.include?("#{reader}")

          if accessors.has_key?(writer)
            module_eval %Q{
              def self.#{writer}=(value)
                @@#{var} = value
              end
            }
            spices_added << "#{writer}=" if track_spices && !spices_added.include?("#{writer}=")
          end
        end
      end
      alias_method :claspiced, :modspiced
    end
  end
end
